#region Apache License
//
// Licensed to the Apache Software Foundation (ASF) under one or more 
// contributor license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright ownership. 
// The ASF licenses this file to you under the Apache License, Version 2.0
// (the "License"); you may not use this file except in compliance with 
// the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
#endregion

using System;
using System.Collections;
using System.Diagnostics;

namespace log4net.Util
{
  /// <summary>
  /// 
  /// </summary>
  /// <param name="source"></param>
  /// <param name="e"></param>
  public delegate void LogReceivedEventHandler(object source, LogReceivedEventArgs e);

  /// <summary>
  /// Outputs log statements from within the log4net assembly.
  /// </summary>
  /// <remarks>
  /// <para>
  /// Log4net components cannot make log4net logging calls. However, it is
  /// sometimes useful for the user to learn about what log4net is
  /// doing.
  /// </para>
  /// <para>
  /// All log4net internal debug calls go to the standard output stream
  /// whereas internal error messages are sent to the standard error output 
  /// stream.
  /// </para>
  /// </remarks>
  /// <author>Nicko Cadell</author>
  /// <author>Gert Driesen</author>
  public sealed class LogLog
  {
    /// <summary>
    /// The event raised when an internal message has been received.
    /// </summary>
    public static event LogReceivedEventHandler LogReceived;

    private readonly Type source;
    private readonly DateTime timeStampUtc;
    private readonly string prefix;
    private readonly string message;
    private readonly Exception exception;

    /// <summary>
    /// The Type that generated the internal message.
    /// </summary>
    public Type Source
    {
      get { return source; }
    }

    /// <summary>
    /// The DateTime stamp of when the internal message was received.
    /// </summary>
    public DateTime TimeStamp
    {
      get { return timeStampUtc.ToLocalTime(); }
    }

    /// <summary>
    /// The UTC DateTime stamp of when the internal message was received.
    /// </summary>
    public DateTime TimeStampUtc
    {
      get { return timeStampUtc; }
    }

    /// <summary>
    /// A string indicating the severity of the internal message.
    /// </summary>
    /// <remarks>
    /// "log4net: ", 
    /// "log4net:ERROR ", 
    /// "log4net:WARN "
    /// </remarks>
    public string Prefix
    {
      get { return prefix; }
    }

    /// <summary>
    /// The internal log message.
    /// </summary>
    public string Message
    {
      get { return message; }
    }

    /// <summary>
    /// The Exception related to the message.
    /// </summary>
    /// <remarks>
    /// Optional. Will be null if no Exception was passed.
    /// </remarks>
    public Exception Exception
    {
      get { return exception; }
    }

    /// <summary>
    /// Formats Prefix, Source, and Message in the same format as the value
    /// sent to Console.Out and Trace.Write.
    /// </summary>
    /// <returns></returns>
    public override string ToString()
    {
      return Prefix + Source.Name + ": " + Message;
    }

    #region Private Instance Constructors

    /// <summary>
    /// Initializes a new instance of the <see cref="LogLog" /> class. 
    /// </summary>
    /// <param name="source"></param>
    /// <param name="prefix"></param>
    /// <param name="message"></param>
    /// <param name="exception"></param>
    public LogLog(Type source, string prefix, string message, Exception exception)
    {
      timeStampUtc = DateTime.UtcNow;

      this.source = source;
      this.prefix = prefix;
      this.message = message;
      this.exception = exception;
    }

    #endregion Private Instance Constructors

    #region Static Constructor

    /// <summary>
    /// Static constructor that initializes logging by reading 
    /// settings from the application configuration file.
    /// </summary>
    /// <remarks>
    /// <para>
    /// The <c>log4net.Internal.Debug</c> application setting
    /// controls internal debugging. This setting should be set
    /// to <c>true</c> to enable debugging.
    /// </para>
    /// <para>
    /// The <c>log4net.Internal.Quiet</c> application setting
    /// suppresses all internal logging including error messages. 
    /// This setting should be set to <c>true</c> to enable message
    /// suppression.
    /// </para>
    /// </remarks>
    static LogLog()
    {
#if !NETCF
      try
      {
        InternalDebugging = OptionConverter.ToBoolean(SystemInfo.GetAppSetting("log4net.Internal.Debug"), false);
        QuietMode = OptionConverter.ToBoolean(SystemInfo.GetAppSetting("log4net.Internal.Quiet"), false);
        EmitInternalMessages = OptionConverter.ToBoolean(SystemInfo.GetAppSetting("log4net.Internal.Emit"), true);
      }
      catch (Exception ex)
      {
        // If an exception is thrown here then it looks like the config file does not
        // parse correctly.
        //
        // We will leave debug OFF and print an Error message
        Error(typeof(LogLog), "Exception while reading ConfigurationSettings. Check your .config file is well formed XML.", ex);
      }
#endif
    }

    #endregion Static Constructor

    #region Public Static Properties

    /// <summary>
    /// Gets or sets a value indicating whether log4net internal logging
    /// is enabled or disabled.
    /// </summary>
    /// <value>
    /// <c>true</c> if log4net internal logging is enabled, otherwise 
    /// <c>false</c>.
    /// </value>
    /// <remarks>
    /// <para>
    /// When set to <c>true</c>, internal debug level logging will be 
    /// displayed.
    /// </para>
    /// <para>
    /// This value can be set by setting the application setting 
    /// <c>log4net.Internal.Debug</c> in the application configuration
    /// file.
    /// </para>
    /// <para>
    /// The default value is <c>false</c>, i.e. debugging is
    /// disabled.
    /// </para>
    /// </remarks>
    /// <example>
    /// <para>
    /// The following example enables internal debugging using the 
    /// application configuration file :
    /// </para>
    /// <code lang="XML" escaped="true">
    /// <configuration>
    ///    <appSettings>
    ///      <add key="log4net.Internal.Debug" value="true" />
    ///    </appSettings>
    /// </configuration>
    /// </code>
    /// </example>
    public static bool InternalDebugging
    {
      get { return s_debugEnabled; }
      set { s_debugEnabled = value; }
    }

    /// <summary>
    /// Gets or sets a value indicating whether log4net should generate no output
    /// from internal logging, not even for errors. 
    /// </summary>
    /// <value>
    /// <c>true</c> if log4net should generate no output at all from internal 
    /// logging, otherwise <c>false</c>.
    /// </value>
    /// <remarks>
    /// <para>
    /// When set to <c>true</c> will cause internal logging at all levels to be 
    /// suppressed. This means that no warning or error reports will be logged. 
    /// This option overrides the <see cref="InternalDebugging"/> setting and 
    /// disables all debug also.
    /// </para>
    /// <para>This value can be set by setting the application setting
    /// <c>log4net.Internal.Quiet</c> in the application configuration file.
    /// </para>
    /// <para>
    /// The default value is <c>false</c>, i.e. internal logging is not
    /// disabled.
    /// </para>
    /// </remarks>
    /// <example>
    /// The following example disables internal logging using the 
    /// application configuration file :
    /// <code lang="XML" escaped="true">
    /// <configuration>
    ///    <appSettings>
    ///      <add key="log4net.Internal.Quiet" value="true" />
    ///    </appSettings>
    /// </configuration>
    /// </code>
    /// </example>
    public static bool QuietMode
    {
      get { return s_quietMode; }
      set { s_quietMode = value; }
    }

    /// <summary>
    /// 
    /// </summary>
    public static bool EmitInternalMessages
    {
      get { return s_emitInternalMessages; }
      set { s_emitInternalMessages = value; }
    }

    #endregion Public Static Properties

    #region Public Static Methods

    /// <summary>
    /// Raises the LogReceived event when an internal messages is received.
    /// </summary>
    /// <param name="source"></param>
    /// <param name="prefix"></param>
    /// <param name="message"></param>
    /// <param name="exception"></param>
    public static void OnLogReceived(Type source, string prefix, string message, Exception exception)
    {
      if (LogReceived != null)
      {
        LogReceived(null, new LogReceivedEventArgs(new LogLog(source, prefix, message, exception)));
      }
    }

    /// <summary>
    /// Test if LogLog.Debug is enabled for output.
    /// </summary>
    /// <value>
    /// <c>true</c> if Debug is enabled
    /// </value>
    /// <remarks>
    /// <para>
    /// Test if LogLog.Debug is enabled for output.
    /// </para>
    /// </remarks>
    public static bool IsDebugEnabled
    {
      get { return s_debugEnabled && !s_quietMode; }
    }

    /// <summary>
    /// Writes log4net internal debug messages to the 
    /// standard output stream.
    /// </summary>
    /// <param name="source"></param>
    /// <param name="message">The message to log.</param>
    /// <remarks>
    /// <para>
    ///  All internal debug messages are prepended with 
    ///  the string "log4net: ".
    /// </para>
    /// </remarks>
    public static void Debug(Type source, string message)
    {
      if (IsDebugEnabled)
      {
        if (EmitInternalMessages)
        {
          EmitOutLine(PREFIX + message);
        }

        OnLogReceived(source, PREFIX, message, null);
      }
    }

    /// <summary>
    /// Writes log4net internal debug messages to the 
    /// standard output stream.
    /// </summary>
    /// <param name="source">The Type that generated this message.</param>
    /// <param name="message">The message to log.</param>
    /// <param name="exception">An exception to log.</param>
    /// <remarks>
    /// <para>
    ///  All internal debug messages are prepended with 
    ///  the string "log4net: ".
    /// </para>
    /// </remarks>
    public static void Debug(Type source, string message, Exception exception)
    {
      if (IsDebugEnabled)
      {
        if (EmitInternalMessages)
        {
          EmitOutLine(PREFIX + message);
          if (exception != null)
          {
            EmitOutLine(exception.ToString());
          }
        }

        OnLogReceived(source, PREFIX, message, exception);
      }
    }

    /// <summary>
    /// Test if LogLog.Warn is enabled for output.
    /// </summary>
    /// <value>
    /// <c>true</c> if Warn is enabled
    /// </value>
    /// <remarks>
    /// <para>
    /// Test if LogLog.Warn is enabled for output.
    /// </para>
    /// </remarks>
    public static bool IsWarnEnabled
    {
      get { return !s_quietMode; }
    }

    /// <summary>
    /// Writes log4net internal warning messages to the 
    /// standard error stream.
    /// </summary>
    /// <param name="source">The Type that generated this message.</param>
    /// <param name="message">The message to log.</param>
    /// <remarks>
    /// <para>
    ///  All internal warning messages are prepended with 
    ///  the string "log4net:WARN ".
    /// </para>
    /// </remarks>
    public static void Warn(Type source, string message)
    {
      if (IsWarnEnabled)
      {
        if (EmitInternalMessages)
        {
          EmitErrorLine(WARN_PREFIX + message);
        }

        OnLogReceived(source, WARN_PREFIX, message, null);
      }
    }

    /// <summary>
    /// Writes log4net internal warning messages to the 
    /// standard error stream.
    /// </summary>
    /// <param name="source">The Type that generated this message.</param>
    /// <param name="message">The message to log.</param>
    /// <param name="exception">An exception to log.</param>
    /// <remarks>
    /// <para>
    ///  All internal warning messages are prepended with 
    ///  the string "log4net:WARN ".
    /// </para>
    /// </remarks>
    public static void Warn(Type source, string message, Exception exception)
    {
      if (IsWarnEnabled)
      {
        if (EmitInternalMessages)
        {
          EmitErrorLine(WARN_PREFIX + message);
          if (exception != null)
          {
            EmitErrorLine(exception.ToString());
          }
        }

        OnLogReceived(source, WARN_PREFIX, message, exception);
      }
    }

    /// <summary>
    /// Test if LogLog.Error is enabled for output.
    /// </summary>
    /// <value>
    /// <c>true</c> if Error is enabled
    /// </value>
    /// <remarks>
    /// <para>
    /// Test if LogLog.Error is enabled for output.
    /// </para>
    /// </remarks>
    public static bool IsErrorEnabled
    {
      get { return !s_quietMode; }
    }

    /// <summary>
    /// Writes log4net internal error messages to the 
    /// standard error stream.
    /// </summary>
    /// <param name="source">The Type that generated this message.</param>
    /// <param name="message">The message to log.</param>
    /// <remarks>
    /// <para>
    ///  All internal error messages are prepended with 
    ///  the string "log4net:ERROR ".
    /// </para>
    /// </remarks>
    public static void Error(Type source, string message)
    {
      if (IsErrorEnabled)
      {
        if (EmitInternalMessages)
        {
          EmitErrorLine(ERR_PREFIX + message);
        }

        OnLogReceived(source, ERR_PREFIX, message, null);
      }
    }

    /// <summary>
    /// Writes log4net internal error messages to the 
    /// standard error stream.
    /// </summary>
    /// <param name="source">The Type that generated this message.</param>
    /// <param name="message">The message to log.</param>
    /// <param name="exception">An exception to log.</param>
    /// <remarks>
    /// <para>
    ///  All internal debug messages are prepended with 
    ///  the string "log4net:ERROR ".
    /// </para>
    /// </remarks>
    public static void Error(Type source, string message, Exception exception)
    {
      if (IsErrorEnabled)
      {
        if (EmitInternalMessages)
        {
          EmitErrorLine(ERR_PREFIX + message);
          if (exception != null)
          {
            EmitErrorLine(exception.ToString());
          }
        }

        OnLogReceived(source, ERR_PREFIX, message, exception);
      }
    }

    #endregion Public Static Methods

    /// <summary>
    /// Writes output to the standard output stream.  
    /// </summary>
    /// <param name="message">The message to log.</param>
    /// <remarks>
    /// <para>
    /// Writes to both Console.Out and System.Diagnostics.Trace.
    /// Note that the System.Diagnostics.Trace is not supported
    /// on the Compact Framework.
    /// </para>
    /// <para>
    /// If the AppDomain is not configured with a config file then
    /// the call to System.Diagnostics.Trace may fail. This is only
    /// an issue if you are programmatically creating your own AppDomains.
    /// </para>
    /// </remarks>
    private static void EmitOutLine(string message)
    {
      try
      {
#if NETCF
        Console.WriteLine(message);
        //System.Diagnostics.Debug.WriteLine(message);
#else
        Console.Out.WriteLine(message);
        Trace.WriteLine(message);
#endif
      }
      catch
      {
        // Ignore exception, what else can we do? Not really a good idea to propagate back to the caller
      }
    }

    /// <summary>
    /// Writes output to the standard error stream.  
    /// </summary>
    /// <param name="message">The message to log.</param>
    /// <remarks>
    /// <para>
    /// Writes to both Console.Error and System.Diagnostics.Trace.
    /// Note that the System.Diagnostics.Trace is not supported
    /// on the Compact Framework.
    /// </para>
    /// <para>
    /// If the AppDomain is not configured with a config file then
    /// the call to System.Diagnostics.Trace may fail. This is only
    /// an issue if you are programmatically creating your own AppDomains.
    /// </para>
    /// </remarks>
    private static void EmitErrorLine(string message)
    {
      try
      {
#if NETCF
        Console.WriteLine(message);
        //System.Diagnostics.Debug.WriteLine(message);
#else
        Console.Error.WriteLine(message);
        Trace.WriteLine(message);
#endif
      }
      catch
      {
        // Ignore exception, what else can we do? Not really a good idea to propagate back to the caller
      }
    }

    #region Private Static Fields

    /// <summary>
    ///  Default debug level
    /// </summary>
    private static bool s_debugEnabled = false;

    /// <summary>
    /// In quietMode not even errors generate any output.
    /// </summary>
    private static bool s_quietMode = false;

    private static bool s_emitInternalMessages = true;

    private const string PREFIX = "log4net: ";
    private const string ERR_PREFIX = "log4net:ERROR ";
    private const string WARN_PREFIX = "log4net:WARN ";

    #endregion Private Static Fields

    /// <summary>
    /// Subscribes to the LogLog.LogReceived event and stores messages
    /// to the supplied IList instance.
    /// </summary>
    public class LogReceivedAdapter : IDisposable
    {
      private readonly IList items;
      private readonly LogReceivedEventHandler handler;

      /// <summary>
      /// 
      /// </summary>
      /// <param name="items"></param>
      public LogReceivedAdapter(IList items)
      {
        this.items = items;

        handler = new LogReceivedEventHandler(LogLog_LogReceived);

        LogReceived += handler;
      }

      void LogLog_LogReceived(object source, LogReceivedEventArgs e)
      {
        items.Add(e.LogLog);
      }

      /// <summary>
      /// 
      /// </summary>
      public IList Items
      {
        get { return items; }
      }

      /// <summary>
      /// 
      /// </summary>
      public void Dispose()
      {
        LogReceived -= handler;
      }
    }
  }

  /// <summary>
  /// 
  /// </summary>
  public class LogReceivedEventArgs : EventArgs
  {
    private readonly LogLog loglog;

    /// <summary>
    /// 
    /// </summary>
    /// <param name="loglog"></param>
    public LogReceivedEventArgs(LogLog loglog)
    {
      this.loglog = loglog;
    }

    /// <summary>
    /// 
    /// </summary>
    public LogLog LogLog
    {
      get { return loglog; }
    }
  }
}
